#include "application.h"

#include "utils.h"
#include "nodetypes.h"
#include "matrix4.h"

#include <iostream>

const unsigned int FPS_PRINT_INTERVAL_MS = 100;
const unsigned int ASSET_REFRESH_INTERVAL_MS = 1000;
const std::string FPS_PREFIX = "FPS: ";
const std::string APPNAME = "RenderHog";
const std::string APPVERSION = "10.10.16";
const std::string APPWEBSITE = "http://renderhog.googlecode.com";
const std::string EXTENSION = "hog";
const std::string RTEXTENSION = "rhr";
const bool treeDebugging = false;

Application::Application(int argc, char ** argv) : newNodeCount(0), changed(false), mouseButtonDown(-1)
{
	
}

int Application::run()
{
	if (!init())
		return 1;
	
	mainWindow.set_title(APPNAME);
	Gtk::Main::run(mainWindow);
	
	deinit();
	
	return 0;
}

bool Application::init()
{
	deinit();
	
	
	writeOutput("Initializing "+APPNAME+" "+APPVERSION);
	
	
	// setup menu bar
	refActionGroup = Gtk::ActionGroup::create();
	refActionGroup->add(Gtk::Action::create("MenuFile", "_File"));
	refActionGroup->add(Gtk::Action::create("New", Gtk::Stock::NEW),
						sigc::mem_fun(*this, &Application::onFileNew));
	refActionGroup->add(Gtk::Action::create("Refresh", Gtk::Stock::REFRESH),
						sigc::mem_fun(*this, &Application::onFileRefresh));
	refActionGroup->add(Gtk::Action::create("Open", Gtk::Stock::OPEN),
						sigc::mem_fun(*this, &Application::onFileOpen));
	refActionGroup->add(Gtk::Action::create("Save", Gtk::Stock::SAVE),
						sigc::mem_fun(*this, &Application::onFileSave));
	refActionGroup->add(Gtk::Action::create("Save As", Gtk::Stock::SAVE_AS),
						sigc::mem_fun(*this, &Application::onFileSaveAs));
	refActionGroup->add(Gtk::Action::create("Export", "Export"),
						sigc::mem_fun(*this, &Application::onFileExport));
	refActionGroup->add(Gtk::Action::create("Quit", Gtk::Stock::QUIT),
						sigc::mem_fun(*this, &Application::onFileQuit));
	refActionGroup->add(Gtk::Action::create("MenuHelp", "_Help"));
	refActionGroup->add(Gtk::Action::create("About", Gtk::Stock::ABOUT),
						sigc::mem_fun(*this, &Application::onHelpAbout));
	Glib::ustring ui_info =
		"<ui>"
		"  <menubar name='MenuBar'>"
		"    <menu action='MenuFile'>"
		"      <menuitem action='New'/>"
		"      <separator/>"
		"      <menuitem action='Refresh'/>"
		"      <separator/>"
		"      <menuitem action='Open'/>"
		"      <menuitem action='Save'/>"
		"      <menuitem action='Save As'/>"
		"      <menuitem action='Export'/>"
		"      <separator/>"
		"      <menuitem action='Quit'/>"
		"    </menu>"
		"    <menu action='MenuHelp'>"
		"      <menuitem action='About'/>"
		"    </menu>"
		"  </menubar>"
		"</ui>";
	uiManager = Gtk::UIManager::create();
	uiManager->insert_action_group(refActionGroup);
	mainWindow.add_accel_group(uiManager->get_accel_group());
	mainWindow.signal_delete_event().connect(sigc::mem_fun(this, &Application::onWindowClose));
	uiManager->add_ui_from_string(ui_info);
	
	
	// setup widgets
	
	Gtk::VBox * mainbox = newWidget<Gtk::VBox>();
	mainWindow.add(*mainbox);
	
	Gtk::Widget * menubar = uiManager->get_widget("/MenuBar");
	mainbox->pack_start(*menubar, Gtk::PACK_SHRINK);
	
	Gtk::HPaned * hpane = newWidget<Gtk::HPaned>();
	mainbox->pack_end(*hpane);
	
	Gtk::Notebook * treetabs = newWidget<Gtk::Notebook>();
	hpane->add1(*treetabs);
	
	Gtk::ScrolledWindow * treewindow = newWidget<Gtk::ScrolledWindow>();
	treewindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
	treetabs->append_page(*treewindow, "Nodes");
	
	tree = newWidget<TreeViewHelper>();
	treewindow->add(*tree);
	
	Gtk::VPaned * vpane = newWidget<Gtk::VPaned>();
	hpane->add2(*vpane);
	
	Gtk::VBox * drawBox = newWidget<Gtk::VBox>();
	Gtk::HBox * toolbarBox = newWidget<Gtk::HBox>();
	
	Gtk::Label * cameraLabel = newWidget<Gtk::Label>();
	cameraLabel->set_text("Camera controls transformation:");
	toolbarBox->pack_start(*cameraLabel, Gtk::PACK_SHRINK, 3);
	cameraDropdown = newWidget<Gtk::ComboBoxText>();
	cameraDropdown->signal_changed().connect(sigc::mem_fun(this, &Application::onCameraChanged));
	toolbarBox->pack_start(*cameraDropdown, Gtk::PACK_SHRINK, 3);
	
	drawarea = newWidget<GLWindow>();
	if (checkErrorCritical(drawarea->getError()))
		return false;
	drawarea->outputWindow.connect(sigc::mem_fun(this, &Application::writeOutput));
	drawarea->getRenderer().logOutput.connect(sigc::mem_fun(this, &Application::writeGlLog));
	drawarea->getRenderer().logClear.connect(sigc::mem_fun(this, &Application::clearGlLog));
	drawarea->add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::SCROLL_MASK | Gdk::POINTER_MOTION_MASK);
	drawarea->signal_button_press_event().connect(sigc::mem_fun(this, &Application::onDrawAreaButtonPress));
	drawarea->signal_button_release_event().connect(sigc::mem_fun(this, &Application::onDrawAreaButtonRelease));
	drawarea->signal_scroll_event().connect(sigc::mem_fun(this, &Application::onDrawAreaScrollEvent));
	drawarea->signal_motion_notify_event().connect(sigc::mem_fun(this, &Application::onDrawAreaMotionEvent));
	drawarea->set_size_request(640,480);
	vpane->pack1(*drawBox, true, true);
	
	drawBox->pack_start(*toolbarBox, Gtk::PACK_SHRINK);
	drawBox->pack_start(*drawarea);
	
	Gtk::VBox * fpsbox = newWidget<Gtk::VBox>();
	fpsbox->pack_start(fpsCounter, Gtk::PACK_SHRINK);
	fpsCounter.set_alignment(Gtk::ALIGN_LEFT, Gtk::ALIGN_TOP);
	fpsCounter.set_text(FPS_PREFIX);
	
	Gtk::Notebook * outputTabs = newWidget<Gtk::Notebook>();
	fpsbox->pack_end(*outputTabs);
	vpane->pack2(*fpsbox, true, true);
	
	outputWindow = newWidget<Gtk::ScrolledWindow>();
	outputWindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
	outputTabs->append_page(*outputWindow, "Full log history");
	
	Gtk::TextView * textout = newWidget<Gtk::TextView>();
	outputBuffer = textout->get_buffer().operator->();
	if (!drawarea->getError().empty())
		writeOutput(drawarea->getError());
	outputWindow->add(*textout);
	
	glLogWindow = newWidget<Gtk::ScrolledWindow>();
	glLogWindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
	outputTabs->append_page(*glLogWindow, "Current logs only");
	
	Gtk::TextView * gltextout = newWidget<Gtk::TextView>();
	glLogBuffer = gltextout->get_buffer().operator->();
	glLogWindow->add(*gltextout);
	
	
	// scene setup
	if (!scene.initialize("", std::cerr))
		return false;
	
	
	// tree setup
	
	tree->set_reorderable(false);
	nodeStore = Glib::RefPtr <TreeStoreHelper>(new TreeStoreHelper(nodeModel));
	nodeStore->signal_row_draggable.connect(sigc::mem_fun(*this, &Application::rowDraggable));
	nodeStore->signal_row_drop_possible.connect(sigc::mem_fun(*this, &Application::rowDropPossible));
	
	if (treeDebugging)
	{
		nodeStore->signal_row_deleted().connect(sigc::mem_fun(*this, &Application::onTreeRowDeleted));
		nodeStore->signal_row_inserted().connect(sigc::mem_fun(*this, &Application::onTreeRowInserted));
		nodeStore->signal_row_changed().connect(sigc::mem_fun(*this, &Application::onTreeRowChanged));
		nodeStore->signal_rows_reordered().connect(sigc::mem_fun(*this, &Application::onTreeRowsReordered));
	}

	//assert(nodeStore->get_flags() & Gtk::TREE_MODEL_ITERS_PERSIST); // don't need to assert if we're using treerowreferences
	
	/*SceneNode * folder = addNode(SceneNode(*scene.getNodeType("Folder"),"folder"));
	addNode(SceneNode(*scene.getNodeType("Texture"),"tex1"), folder);
	addNode(SceneNode(*scene.getNodeType("Texture"),"tex2"));*/
	
	tree->set_model(nodeStore);
	{
		Gtk::TreeView::Column * pColumn = Gtk::manage( new Gtk::TreeView::Column("") );
		pColumn->pack_start(nodeModel.icon, false);
		pColumn->pack_start(nodeModel.name);
		tree->append_column(*pColumn);
	}
	
	treeSelection = tree->get_selection();
	treeSelection->set_mode(Gtk::SELECTION_SINGLE);
	
	tree->enable_model_drag_source();
	tree->enable_model_drag_dest();
	
	tree->signal_row_activated().connect(sigc::mem_fun(*this, &Application::onTreeRowActivated));
	tree->composite_signal_button_press_event.connect(sigc::mem_fun(*this, &Application::onTreeButtonPress));
	//tree->signal_drag_drop().connect_notify(sigc::mem_fun(*this, &Application::onTreeDragFinish));
	//tree->signal_drag_end().connect_notify(sigc::mem_fun(*this, &Application::onTreeDragFinish));
	tree->composite_signal_drag_drop.connect(sigc::mem_fun(*this, &Application::onTreeDragFinish));
	
	
	// init FPS printing timer
	Glib::signal_timeout().connect(sigc::mem_fun(*this, &Application::onFpsPrintTimeout), FPS_PRINT_INTERVAL_MS);
	
	// init asset refresh timer
	Glib::signal_timeout().connect(sigc::mem_fun(*this, &Application::onAssetRefreshTimeout), ASSET_REFRESH_INTERVAL_MS);
	
	
	hpane->set_position(200);
	
	
	writeOutput("Initialization complete");
	
	
	mainWindow.show_all();
	
	
	return true;
}

void Application::deinit()
{
	
}

void Application::writeOutput(std::string text)
{
	// let's echo everything to stdout
	std::cout << APPNAME << " Log: " << text << std::endl;
	
	if (outputBuffer && outputWindow)
	{
		Gtk::TextBuffer::iterator begin, end;
		outputBuffer->get_bounds(begin, end);
		outputBuffer->place_cursor(end);
		outputBuffer->insert_at_cursor(text+"\n");
		outputBuffer->get_bounds(begin, end);
		outputBuffer->place_cursor(end);
		outputWindow->get_vadjustment()->set_value(outputWindow->get_vadjustment()->get_upper());
	}
}

void Application::writeGlLog(std::string text)
{
	writeOutput(text);
	
	if (outputBuffer && outputWindow)
	{
		Gtk::TextBuffer::iterator begin, end;
		glLogBuffer->get_bounds(begin, end);
		glLogBuffer->place_cursor(end);
		glLogBuffer->insert_at_cursor(text+"\n");
		glLogBuffer->get_bounds(begin, end);
		glLogBuffer->place_cursor(end);
		glLogWindow->get_vadjustment()->set_value(glLogWindow->get_vadjustment()->get_upper());
	}
}

void Application::clearGlLog()
{
	if (glLogBuffer && glLogWindow)
	{
		glLogBuffer->set_text("");
	}
}

bool Application::checkErrorCritical(const std::string & text)
{
	if (text.empty())
		return false;
	else
	{
		writeOutput(text);
		return true;
	}
}

Gtk::TreeModel::Row Application::getRowForNode(const SceneNode * node)
{
	rowLookup_type::iterator i = rowLookup.find(node);
	if (i != rowLookup.end())
	{
		Gtk::TreeModel::Path path = i->second.get_path();
		if (path)
		{
			return *nodeStore->get_iter(path);
		}
		else
		{
			std::cerr << "Row reference went away!" << std::endl;
			assert(0);
		}
	}
	
	return Gtk::TreeModel::Row();
}

const SceneNode * Application::getNodeForRowConst(const Gtk::TreeModel::Row & row) const
{
	return row[nodeModel.ptr];
}

SceneNode * Application::addNode(const SceneNode & node, SceneNode * parent, const SceneNode * beforeNodeIfPossible)
{
	SceneNode * newNode(NULL);
	
	// add the node to the scene
	SceneNode nodeNoChildren;
	nodeNoChildren.copyWithoutChildren(node);
	reseatable_reference <SceneNode> newNodeRef;
	if (!parent)
		newNodeRef = scene.addNode(nodeNoChildren, beforeNodeIfPossible);
	else
		newNodeRef = parent->addNode(nodeNoChildren, beforeNodeIfPossible);
	
	if (newNodeRef)
	{
		newNode = &newNodeRef.get();
		
		Gtk::TreeModel::Row parentRow;
		if (parent)
		{
			parentRow = getRowForNode(parent);
		}
		
		Gtk::TreeModel::iterator iter;
		
		// add the row to the tree model
		Gtk::TreeModel::iterator treeBegin = parentRow ? parentRow->children().begin() : nodeStore->children().begin();
		Gtk::TreeModel::iterator treeEnd = parentRow ? parentRow->children().end() : nodeStore->children().end();
		Gtk::TreeModel::iterator treeInsertLocation = treeEnd;
		Gtk::TreeModel::Row beforeRow = getRowForNode(beforeNodeIfPossible);
		if (beforeRow)
		{
			for (Gtk::TreeModel::iterator i = treeBegin; i != treeEnd; i++)
			{
				if (i == *beforeRow)
				{
					treeInsertLocation = i;
					if (treeDebugging) std::cout << "Found tree insert location" << std::endl;
					break;
				}
			}
		}
		iter = nodeStore->insert(treeInsertLocation);
		
		// set row values
		Gtk::TreeModel::Row row = *iter;
		row[nodeModel.name] = node.getName();
		row[nodeModel.ptr] = newNode;
		std::string iconId = newNode->getIcon();
		iconMap_type::iterator i = iconMap.find(iconId);
		if (i == iconMap.end())
		{
			Glib::RefPtr<Gdk::Pixbuf> newIcon = mainWindow.render_icon(Gtk::StockID(iconId), Gtk::ICON_SIZE_MENU);
			iconMap[iconId] = newIcon;
			row[nodeModel.icon] = newIcon;
		}
		else
			row[nodeModel.icon] = i->second;
		
		// remember the link between the row and the node
		rowLookup[newNode] = Gtk::TreeRowReference(nodeStore, nodeStore->get_path(iter));
		
		if (treeDebugging)
			std::cout << "added node " << newNode->getName() << " path: " << nodeStore->get_path(iter).to_string() << std::endl;
		
		// recurse into children
		std::vector <const SceneNode*> children;
		node.getChildren(children, false);
		for (std::vector <const SceneNode*>::const_iterator i = children.begin(); i != children.end(); i++)
		{
			addNode(**i, newNode);
		}
	}
	
	return newNode;
}

bool Application::deleteNode(const SceneNode & node)
{
	assert(&node != &scene.getRoot());
	
	// get the associated row
	rowLookup_type::iterator i = rowLookup.find(&node);
	if (i == rowLookup.end())
	{
		// the node should always be in the lookup
		assert(0);
		
		return false;
	}
	
	// check for corruption
	if (treeDebugging)
		std::cout << "deleting node " << i->first->getName() << " path: " << i->second.get_path().to_string() << std::endl;
	assert(i->second.get_path());
	/*for (rowLookup_type::iterator n = rowLookup.begin(); n != rowLookup.end(); n++)
	{
		if (n != i)
		{
			if (n->second.get_path() == i->second.get_path())
			{
				std::cerr << "row to be deleted (i): " << i->first->getName() << " path: " << i->second.get_path().to_string() << std::endl;
				std::cerr << "existing node     (n): " << n->first->getName() << " path: " << n->second.get_path().to_string() << std::endl;
				assert(0);
			}
		}
	}*/
	
	Gtk::TreeModel::Path path = i->second.get_path();
	if (!path)
	{
		std::cerr << "Invalid path: \"" << path.to_string() << "\"" << std::endl;
		return false;
	}
	
	// remove the link between the row and the node
	// also remove the row lookup entries for all children
	rowLookup.erase(i);
	std::vector <const SceneNode*> children;
	node.getChildren(children);
	for (std::vector <const SceneNode*>::iterator c = children.begin(); c != children.end(); c++)
		rowLookup.erase(*c);
	
	// remove the row from the model
	// this should also remove child rows (?)
	Gtk::TreeModel::iterator iter = nodeStore->get_iter(path);
	if (!iter)
	{
		std::cerr << "Couldn't get iterator for path: \"" << path.to_string() << "\"" << std::endl;
		return false;
	}
	nodeStore->erase(iter);
	
	// remove the node from the scene
	// this will also remove child rows
	return scene.deleteNode(node);
}

void Application::onTreeRowActivated(const Gtk::TreeModel::Path & path, Gtk::TreeViewColumn * /* model */)
{
	Gtk::TreeModel::Row row = *tree->get_model()->get_iter(path);
	SceneNode * node = getNodeForRow(row);
	assert(node);
	onTreeRowEdit(node);
}

void Application::handleNodeRename(const SceneNode & node, const std::string & oldname)
{
	SceneNode * parent = scene.getParent(node);
	if (parent)
		parent->handleNodeRename(node, oldname);
}

void Application::onTreeRowEdit(SceneNode * node)
{
	assert(node);
	if (node)
	{
		NodeEditor editor;
		std::string oldname = node->getName();
		if (editor.editNode(*node, scene.getRoot()))
		{
			Gtk::TreeModel::Row row = getRowForNode(node);
			row[nodeModel.name] = node->getName();
			if (node->getName() != oldname)
				handleNodeRename(*node, oldname);
			onSceneChanged();
		}
	}
}

bool Application::onTreeButtonPress(GdkEventButton * event)
{
	// display a pop-up context menu
	if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3))
	{
		Gtk::TreeModel::Path origClickPath;
		bool rowAtClick = tree->get_path_at_pos(event->x, event->y, origClickPath);
		//std::cout << (int) rowAtClick << ": " << origClickPath.to_string() << std::endl;
		
		if (popupMenu)
			popupMenu->popdown();
		popupMenu.reset(new Gtk::Menu());
		
		if (!rowAtClick)
			treeDeselect();
		
		SceneNode * node = getSelectedNode();
		if (!node)
			node = &scene.getRoot();
		assert(node);
		const bool nodeIsRoot = (node == &scene.getRoot());
		
		Gtk::Menu::MenuList& menulist = popupMenu->items();
		
		// build the menu items
		
		if (!nodeIsRoot)// && node->isEditable()) // all nodes are editable because they have an implicit "name" field... for now, anyway
		{
			menulist.push_back( Gtk::Menu_Helpers::MenuElem("Edit",
				sigc::bind(sigc::mem_fun(*this, &Application::onTreeRowEdit), node)));
		}
		
		// build all of the add node options
		
		// find a node to use as the parent node (by default we try adding under the selected node, but we
		// fall back if the selected node can't have any children)
		SceneNode * addToNode = node;
		if (!addToNode->canHaveChildren())
		{
			addToNode = scene.getParent(*addToNode);
			// use the parent of the selected node as the place to add the node, unless we can't find
			// a parent
			if (!addToNode)
				addToNode = node;
		}
		
		// build a list of nodes for each category that the node supports
		// these will be submenus
		typedef std::list <std::pair<std::string,const SceneNode*> > supportedType_type;
		typedef std::map <std::string, supportedType_type> supportedCategories_type;
		std::map <std::string, std::list <std::pair<std::string,const SceneNode*> > > supportedCategories; // the pair is (typename, prototype*)
		for (SceneManager::nodeTypes_type::const_iterator i = scene.getNodeTypes().begin(); i != scene.getNodeTypes().end(); i++)
		{
			if (addToNode->canHaveChild(i->second))
			{
				supportedCategories[i->second.getCategory()].push_back(std::make_pair(i->first,&i->second));
			}
		}
		
		// iterate over categories
		for (supportedCategories_type::const_iterator i = supportedCategories.begin(); i != supportedCategories.end(); i++)
		{
			// don't bother creating a submenu for a single item
			if (i->second.size() == 1)
			{
				const std::string & type = i->second.back().first;
				Gtk::Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(std::string("Add ")+type+" node",
					sigc::bind(sigc::mem_fun(*this, &Application::addNodeAtSelected), type) );
				menulist.push_back(elem);
				menulist.back().set_tooltip_text(i->second.back().second->getDescription());
			}
			else
			{
				Gtk::Menu * submenu = newWidget<Gtk::Menu>();
				
				// iterate over types
				for (supportedType_type::const_iterator n = i->second.begin(); n != i->second.end(); n++)
				{
					Gtk::Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(std::string("Add ")+n->first,
						sigc::bind(sigc::mem_fun(*this, &Application::addNodeAtSelected), n->first) );
					submenu->items().push_back(elem);
					submenu->items().back().set_tooltip_text(n->second->getDescription());
				}
				
				Gtk::Menu_Helpers::MenuElem submenuelem = Gtk::Menu_Helpers::MenuElem(std::string("Add ")+i->first+" node", *submenu);
				menulist.push_back(submenuelem);
			}
		}
		
		if (!nodeIsRoot)
		{
			menulist.push_back( Gtk::Menu_Helpers::StockMenuElem(Gtk::Stock::CUT,
				sigc::mem_fun(*this, &Application::cutSelectedNode) ) );
				
			menulist.push_back( Gtk::Menu_Helpers::StockMenuElem(Gtk::Stock::COPY,
				sigc::mem_fun(*this, &Application::copySelectedNode) ) );
		}
		
		if (copiedNode && addToNode->canHaveChild(*copiedNode))
		{
			menulist.push_back( Gtk::Menu_Helpers::StockMenuElem(Gtk::Stock::PASTE,
				sigc::mem_fun(*this, &Application::pasteSelectedNode) ) );
		}
		
		if (!nodeIsRoot)
		{
			menulist.push_back( Gtk::Menu_Helpers::StockMenuElem(Gtk::Stock::REFRESH,
				sigc::mem_fun(*this, &Application::refreshSelectedNode) ) );
		}
		
		if (!nodeIsRoot && node->isResettable())
		{
			menulist.push_back( Gtk::Menu_Helpers::MenuElem("Reset to defaults",
				sigc::mem_fun(*this, &Application::resetSelectedNode) ) );
		}
	
		if (!nodeIsRoot)
		{
			menulist.push_back( Gtk::Menu_Helpers::StockMenuElem(Gtk::Stock::DELETE,
				sigc::mem_fun(*this, &Application::deleteSelectedNode) ) );
		}
		
		popupMenu->popup(event->button, event->time);
		
		return true;
	}
	else
		return false;
}

SceneNode * Application::getSelectedNode()
{
	Gtk::TreeModel::iterator iter = treeSelection->get_selected();
	if (!iter)
		return false;
	const Gtk::TreeModel::Row row = *iter;
	
	SceneNode * node = NULL;
	node = getNodeForRow(row);
	return node;
}

void Application::cutSelectedNode()
{
	SceneNode * node = getSelectedNode();
	if (node)
	{
		copiedNode = std::tr1::shared_ptr<SceneNode>(new SceneNode(*node));
		deleteSelectedNode();
	}
	else
	{
		writeError("Can't cut node: none selected");
	}
}

void Application::copySelectedNode()
{
	SceneNode * node = getSelectedNode();
	if (node)
	{
		copiedNode = std::tr1::shared_ptr<SceneNode>(new SceneNode(*node));
	}
	else
	{
		writeError("Can't copy node: none selected");
	}
}

void Application::pasteSelectedNode()
{
	if (!copiedNode)
	{
		writeError("Can't paste node: copy/cut buffer is empty");
		return;
	}
	
	SceneNode * parent = getSelectedNode();
	
	// if we can't have children, we're probably a sibling, not a parent
	if (parent && !parent->canHaveChildren())
		parent = scene.getParent(*parent);
	
	if (!parent)
		parent = &scene.getRoot();
	
	if (!addNode(SceneNode(*copiedNode), parent))
	{
		writeError("Can't paste node");
	}
	else
	{
		onSceneChanged();
		copiedNode.reset();
	}
}

void Application::refreshSelectedNode()
{
	SceneNode * node = getSelectedNode();
	if (node)
	{
		SceneNode * parent = scene.getParent(*node);
		const SceneNode * sibling = scene.getNextSibling(*node);
		if (!parent)
			writeError("Can't refresh root node");
		else
		{
			SceneNode backup = *node;
			if (!deleteNode(*node))
				writeError("Error preparing for node refresh");
			else
			{
				drawarea->sendSceneToRenderer(scene.getRoot());
				if (!addNode(backup, parent, sibling))
					writeError("Unable to finish node refresh; node has been lost!");
				drawarea->sendSceneToRenderer(scene.getRoot());
			}
		}
	}
	else
	{
		writeError("Can't refresh node: none selected");
	}
}

void Application::resetSelectedNode()
{
	SceneNode * node = getSelectedNode();
	if (node)
	{
		if (node->isResettable())
		{
			std::string oldname = node->getName();
			*node = *scene.getNodeType(node->getType());
			node->setName(oldname);
			onSceneChanged();
		}
		else
			writeError("Node isn't resettable");
	}
	else
	{
		writeError("Can't reset node: none selected");
	}
}

void Application::deleteSelectedNode()
{
	SceneNode * node = getSelectedNode();
	if (node)
	{
		if (!deleteNode(*node))
			writeError("Node deletion failed");
		else
			onSceneChanged();
	}
	else
	{
		writeError("Can't delete node: none selected");
	}
}

void Application::addNodeAtSelected(const std::string & type)
{
	SceneNode * parent = getSelectedNode();
	
	// if we can't have children, we're probably a sibling, not a parent
	if (parent && !parent->canHaveChildren())
		parent = scene.getParent(*parent);
	
	// at this point, parent may be NULL, which is handled by addNode
	// to mean add to the root node, which is what we want
	
	reseatable_reference <const SceneNode> prototype = scene.getNodeType(type);
	if (!prototype)
	{
		writeError("Can't add node: unknown type "+type);
		return;
	}
	
	if (!addNode(SceneNode(*prototype, type+"_"+tostr(newNodeCount+1)), parent))
	{
		writeError("Can't add node "+type);
	}
	else
	{
		onSceneChanged();
	}
	
	newNodeCount++;
}

void Application::onTreeDragFinish(const Glib::RefPtr<Gdk::DragContext>& context/*, int, int, unsigned int*/)
{
	if (treeDebugging)
	{
		std::cout << (std::string("Drag from ")+treeDragSource.to_string()+" to "+treeDragDest.to_string()) << std::endl; //return;
	}
	
	SceneNode * parentNode = &scene.getRoot();
	if (!treeDragDest.empty())
		parentNode = getNodeForRow(*nodeStore->get_iter(treeDragDest));
	assert(parentNode);
	
	SceneNode * sourceNode = NULL;
	assert(!treeDragSource.empty());
	sourceNode = getNodeForRow(*nodeStore->get_iter(treeDragSource));
	assert(sourceNode);
	
	SceneNode * oldParent = scene.getParent(*sourceNode);
	assert(oldParent);
	
	if (treeDebugging)
	{
		std::cout << "{" << std::endl;
		for (rowLookup_type::iterator i = rowLookup.begin(); i != rowLookup.end(); i++)
		{
			std::cout << "\t" << i->first->getName() << " path " << (i->second.get_path() ? i->second.get_path().to_string() : "NULL") << std::endl;
		}
		std::cout << "}" << std::endl;
	}
	
	assert(sourceNode != parentNode);
	
	// check for NOOP
	/*if (sourceNode == parentNode || scene.getParent(*sourceNode) == parentNode)
		return;*/
	// we must delete and add even for NOOPs because the stupid treerowreferences WILL
	// BECOME INVALID for ANY DRAG AND DROP!! ZOMG WHO IS IN CHARGE HERE
	
	// delete the node, then re-add it
	SceneNode tempNode = *sourceNode;
	if (deleteNode(*sourceNode))
	{
		if (!addNode(tempNode, parentNode, rowDropInsertBeforeNodeIfPossible ? &rowDropInsertBeforeNodeIfPossible.get() : NULL))
		{
			// okay, we probably dragged it to an invalid location
			// put it back where it was
			
			if (!addNode(tempNode, oldParent))
				writeError("Unable to re-add node during failed move of "+sourceNode->getName());
		}
		
		onSceneChanged();
	}
	else
		writeError("Unable to delete node during move of "+sourceNode->getName());
}

bool Application::rowDraggable(const Gtk::TreeModel::Path& path) const
{
	return true;
}

bool Application::rowDropPossible(const Gtk::TreeModel::Path& dest, const Gtk::SelectionData& selection_data) const
{
	rowDropInsertBeforeNodeIfPossible.clear();
	
	Gtk::TreeModel::Path source;
	Gtk::TreeModel::Path::get_from_selection_data(selection_data, source);
	treeDragSource = source;
	
	if (treeDebugging)
		std::cout << (std::string("rowDropPossible ")+dest.to_string()+" "+source.to_string()) << std::endl;
	
	Gtk::TreeModel::Path dest_parent = dest;
	treeDragDest = dest_parent;
	if (dest_parent.empty())
		return true; // always allow dropping under the root
	
	// if we are going to reorder nodes, we need to know where to insert the dropped node
	Gtk::TreeModel::iterator dropIter = nodeStore->get_iter(dest_parent);
	if (dropIter)
		rowDropInsertBeforeNodeIfPossible = getNodeForRowConst(*dropIter);
	
	Gtk::TreeModel::iterator iter;
	if (!iter)
	{
		bool dest_is_not_top_level = dest_parent.up();
		treeDragDest = dest_parent;
		if (!dest_is_not_top_level || dest_parent.empty())
			return true; // always allow dropping under the root
		iter = nodeStore->get_iter(dest_parent);
		if (!iter)
			return false;
	}
	const SceneNode * parentNode = getNodeForRowConst(*iter);
	assert(parentNode);
	
	const SceneNode * sourceNode = getNodeForRowConst(*nodeStore->get_iter(source));
	assert(sourceNode);
	
	// don't allow movement into descendants
	if (source.is_ancestor(dest))
		return false;
	
	// don't allow movement into ourself
	if (parentNode == sourceNode)
		return false;
	
	return parentNode->canHaveChild(*sourceNode);
}

bool Application::onFpsPrintTimeout()
{
	std::stringstream f;
	f.setf(std::ios::fixed, std::ios::floatfield);
	f.precision(2);
	f << drawarea->getFps();
	fpsCounter.set_text(FPS_PREFIX+f.str());
	return true;
}

bool Application::onAssetRefreshTimeout()
{
	drawarea->sendSceneToRenderer(scene.getRoot(), true);
	return true;
}

bool Application::checkForSave()
{
	if (changed)
	{
		Gtk::MessageDialog dialog("The current document has unsaved work.", false, Gtk::MESSAGE_QUESTION,
								  Gtk::BUTTONS_YES_NO, true);
		dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
		dialog.set_title("Save first?");
		dialog.set_secondary_text("Would you like to save the current document first?");
		int result = dialog.run();
		
		if (result == Gtk::RESPONSE_CANCEL)
			return true;
		
		if (result == Gtk::RESPONSE_YES)
			return !fileSave();
	}
	
	return false;
}

void Application::onFileNew()
{
	if (checkForSave())
		return;
	
	std::vector <const SceneNode*> nodesToDelete;
	const bool recurse = false;
	scene.getRoot().getChildren(nodesToDelete, recurse);
	for (std::vector <const SceneNode*>::iterator i = nodesToDelete.begin(); i != nodesToDelete.end(); i++)
	{
		if (!deleteNode(**i))
		{
			writeError("Couldn't delete a node during onFileNew()");
		}
	}
	newNodeCount = 0;
	onSceneChanged();
	changed = false;
}

void Application::onFileOpen()
{
	if (checkForSave())
		return;
	
	Gtk::FileChooserDialog dialog("Please choose a file",Gtk::FILE_CHOOSER_ACTION_OPEN);
	dialog.set_transient_for(mainWindow);
	
	//Add response buttons the the dialog:
	dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
	dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
	
	//Add filters, so that only certain file types can be selected:
	Gtk::FileFilter filter_app;
	filter_app.set_name(APPNAME+" files"+" (."+EXTENSION+")");
	filter_app.add_pattern(std::string("*.")+EXTENSION);
	dialog.add_filter(filter_app);
	
	Gtk::FileFilter filter_any;
	filter_any.set_name("All files");
	filter_any.add_pattern("*");
	dialog.add_filter(filter_any);
	
	//Show the dialog and wait for a user response:
	int result = dialog.run();
	
	//Handle the response:
	switch(result)
	{
		case(Gtk::RESPONSE_OK):
		{
			std::string filename = dialog.get_filename();
			std::stringstream log;
			SceneNode newNode;
			if (scene.load(newNode, filename, log))
			{
				changed = false;
				onFileNew();
				
				std::vector <const SceneNode*> nodes;
				const bool recurse = false;
				newNode.getChildren(nodes, recurse);
				for (std::vector <const SceneNode*>::iterator i = nodes.begin(); i != nodes.end(); i++)
				{
					if (!addNode(**i))
					{
						writeError("Couldn't add a node during onFileOpen()");
					}
				}
				
				writeOutput(std::string("Loaded ")+filename);
				sceneFile = filename;
				
				onSceneChanged();
				
				changed = false;
			}
			else
			{
				writeError(log.str());
			}
			break;
		}
		case(Gtk::RESPONSE_CANCEL):
		{
			break;
		}
		default:
		{
			break;
		}
	}
}

void Application::onFileSave()
{
	fileSave();
}

bool Application::fileSave()
{
	if (sceneFile.empty())
		return fileSaveAs();
	else
	{
		std::string filename = sceneFile;
		std::stringstream log;
		if (scene.save(filename, log))
		{
			writeOutput(std::string("Saved ")+filename);
			changed = false;
		}
		else
		{
			writeError(std::string("Couldn't save ")+filename+": "+log.str());
		}
		return true;
	}
}

void Application::onFileSaveAs()
{
	fileSaveAs();
}

bool Application::fileSaveAs()
{
	Gtk::FileChooserDialog dialog("Please choose a file",Gtk::FILE_CHOOSER_ACTION_SAVE);
	dialog.set_transient_for(mainWindow);
	
	//Add response buttons the the dialog:
	dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
	dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
	
	//Add filters, so that only certain file types can be selected:
	Gtk::FileFilter filter_app;
	filter_app.set_name(APPNAME+" files"+" (."+EXTENSION+")");
	filter_app.add_pattern(std::string("*.")+EXTENSION);
	dialog.add_filter(filter_app);
	
	//Show the dialog and wait for a user response:
	int result = dialog.run();
	
	//Handle the response:
	switch(result)
	{
		case(Gtk::RESPONSE_OK):
		{
			std::string filename = dialog.get_filename();
			if (filename.find(".") == std::string::npos)
				filename = filename+"."+EXTENSION;
			std::stringstream log;
			if (scene.save(filename, log))
			{
				writeOutput(std::string("Saved ")+filename);
				sceneFile = filename;
				changed = false;
			}
			else
			{
				writeError(log.str());
			}
			return true;
			break;
		}
		default:
		{
			return false;
			break;
		}
	}
}

void Application::onFileExport()
{
	Gtk::FileChooserDialog dialog("Please choose a file",Gtk::FILE_CHOOSER_ACTION_SAVE);
	dialog.set_transient_for(mainWindow);
	
	//Add response buttons the the dialog:
	dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
	dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
	
	//Add filters, so that only certain file types can be selected:
	Gtk::FileFilter filter_app;
	filter_app.set_name(APPNAME+" RealTime files"+" (."+RTEXTENSION+")");
	filter_app.add_pattern(std::string("*.")+RTEXTENSION);
	dialog.add_filter(filter_app);
	
	//Show the dialog and wait for a user response:
	int result = dialog.run();
	
	//Handle the response:
	switch(result)
	{
		case(Gtk::RESPONSE_OK):
		{
			std::string filename = dialog.get_filename();
			if (filename.find(".") == std::string::npos)
				filename = filename+"."+RTEXTENSION;
			std::stringstream log;
			if (drawarea->getRenderer().exportRealtime(scene.getRoot(), filename, log))
			{
				writeOutput(std::string("Exported ")+filename);
			}
			else
			{
				writeError(log.str());
			}
			break;
		}
		default:
		{
			break;
		}
	}
}

void Application::onFileQuit()
{
	if (checkForSave())
		return;
	Gtk::Main::quit();
}

void Application::treeDeselect()
{
	treeSelection->unselect_all();
}

void Application::onTreeRowDeleted(const Gtk::TreeModel::Path& path)
{
	std::cout << "onTreeRowDeleted: " << path.to_string() << std::endl;
}

void Application::onTreeRowInserted(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter)
{
	std::cout << "onTreeRowInserted: " << path.to_string() << std::endl;
}

void Application::onTreeRowChanged(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter)
{
	Gtk::TreeModel::Row row = *iter;
	Glib::ustring n = row[nodeModel.name];
	std::string name1 = n;
	SceneNode * node = row[nodeModel.ptr];
	std::string name2("NULL");
	if (node)
		name2 = node->getName();
	std::cout << "onTreeRowChanged: " << path.to_string() << " " << name1 << "/" << name2 << std::endl;
}

void Application::onTreeRowsReordered(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter, int* new_order)
{
	std::cout << "onTreeRowsReordered: " << path.to_string() << std::endl;
}

void Application::onSceneChanged()
{
	changed = true;
	sendSceneToRenderer();
}

bool Application::onWindowClose(GdkEventAny* event)
{
	onFileQuit();
	return true;
}

void Application::sendSceneToRenderer()
{
	drawarea->sendSceneToRenderer(scene.getRoot());
	
	// update the camera dropdown list
	std::string lastCamera = cameraDropdown->get_active_text();
	cameraDropdown->clear_items();
	cameraDropdown->append_text("(disabled)");
	std::vector <const SceneNode *> nodes;
	std::vector <std::string> paths;
	scene.getRoot().getChildren(nodes, 
								true, 
								NodeTypes::getType(NodeTypes::TRANSFORMATION),
								NULL,
								"",
								&paths);
	assert(paths.size() == nodes.size());
	for (int i = 0; i < nodes.size(); i++)
	{
		cameraDropdown->append_text(paths[i]);
	}
	if (lastCamera.empty())
		lastCamera = "(disabled)";
	cameraDropdown->set_active_text(lastCamera);
	
	onCameraChanged();
}

void Application::onFileRefresh()
{
	drawarea->sendSceneToRenderer(SceneNode());
	drawarea->sendSceneToRenderer(scene.getRoot());
}

void Application::onCameraChanged()
{
	std::string lastCamera = cameraDropdown->get_active_text();
	std::vector <const SceneNode *> nodes;
	std::vector <std::string> paths;
	scene.getRoot().getChildren(nodes, 
								true, 
								NodeTypes::getType(NodeTypes::TRANSFORMATION),
								NULL,
								"",
								&paths);
	curCameraNode.clear();
	assert(paths.size() == nodes.size());
	for (int i = 0; i < nodes.size(); i++)
	{
		if (lastCamera == paths[i])
		{
			curCameraNode = const_cast<SceneNode*>(nodes[i]);
		}
	}
}

bool Application::onDrawAreaButtonPress(GdkEventButton * event)
{
	if (event->type == GDK_BUTTON_PRESS)
	{
		if (mouseButtonDown == -1)
		{
			//std::cout << "Down: " << event->button << std::endl;
			mouseButtonDown = event->button;
			mouseDownLocation[0] = event->x;
			mouseDownLocation[1] = event->y;
		}
		return true;
	}
	else
		return false;
}

bool Application::onDrawAreaButtonRelease(GdkEventButton * event)
{
	if (event->type == GDK_BUTTON_RELEASE)
	{
		if (event->button == mouseButtonDown)
		{
			//std::cout << "Up: " << event->button << std::endl;
			mouseButtonDown = -1;
		}
		return true;
	}
	else
		return false;
}

enum MOVEMENT_TYPE
{
	ROTATE,
	MOVEZ
};

void handleMouseMovement(const MATHVECTOR <double, 2> & delta, SceneNode & curCameraNode, MOVEMENT_TYPE movement)
{
	curCameraNode.updateFloatVars();
	if (curCameraNode.getFloatVars()->size() == 16)
	{
		MATRIX4 <double> curMatrix;
		curMatrix.Set(&(*curCameraNode.getFloatVars())[0]);
		
		switch (movement)
		{
			case MOVEZ:
			{
				// find the forward vector
				// Oh hey, the forward vector is encoded in the
				// orthonormal basis vectors that make up a
				// 3x3 rotation matrix!
				MATHVECTOR <float, 3> forward;
				forward.Set(&curMatrix[8]);
				forward = forward * delta[0];
				MATHVECTOR <float, 3> curPos;
				curPos.Set(&curMatrix[12]);
				curPos = curPos + forward;
				for (int i = 0; i < 3; i++)
					curMatrix[12+i] = curPos[i];
			}
			break;
			
			case ROTATE:
			{
				MATRIX4 <double> rot1;
				rot1.SetRotation(-delta[0]*0.01, 0, 1, 0);
				
				MATRIX4 <double> rot2;
				rot2.SetRotation(-delta[1]*0.01, 1, 0, 0);
				
				//curMatrix = rot2.Multiply(rot1).Multiply(curMatrix);
				curMatrix = curMatrix.Multiply(rot1).Multiply(rot2);
			}
			break;
		};
		
		// keep it orthonormal
		curMatrix.OrthoNormalize();
		
		// keep it affine
		curMatrix.ForceAffine();
		
		curCameraNode.setFloatVars(curMatrix.GetArray());
	}
}

bool Application::onDrawAreaScrollEvent(GdkEventScroll* event)
{
	MATHVECTOR <double, 2> delta;
	if (event->direction == GDK_SCROLL_UP)
		delta[0] = 1.0;
	else if (event->direction == GDK_SCROLL_DOWN)
		delta[0] = -1.0;
	
	if (curCameraNode)
		handleMouseMovement(delta, *curCameraNode, MOVEZ);
	
	return false;
}

bool Application::onDrawAreaMotionEvent(GdkEventMotion* event)
{
	if (mouseButtonDown > 0)
	{
		MATHVECTOR <double, 2> newLoc;
		newLoc[0] = event->x;
		newLoc[1] = event->y;
		
		MATHVECTOR <double, 2> delta = newLoc - mouseDownLocation;
		
		if (curCameraNode)
			handleMouseMovement(delta, *curCameraNode, ROTATE);
		
		mouseDownLocation = newLoc;
		
		return true;
	}
	
	return false;
}

void Application::onHelpAbout()
{
	Gtk::MessageDialog dialog(APPNAME + " " + APPVERSION);
	dialog.set_secondary_text("by Joe Venzon\n"+APPWEBSITE);

	dialog.run();
}
